package cn.k7g.alloy.mold.impl;

import cn.k7g.alloy.annotation.MoldMethod;
import cn.k7g.alloy.annotation.MoldObject;
import cn.k7g.alloy.expose.DataProvider;
import cn.k7g.alloy.mold.BaseMold;
import cn.k7g.alloy.mold.MoldConfig;
import cn.k7g.alloy.mold.MoldService;
import cn.k7g.alloy.mold.var.AbsVar;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.NoSuchMessageException;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.support.ReflectiveMethodExecutor;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * AbstractMoldService
 * @date  2021/2/5 下午1:46
 * @author victor-wu
 */
public abstract class AbstractMoldService implements MoldService {

    @Autowired
    protected ApplicationContext context;
    @Autowired
    protected MoldConfig moldConfig;

    final Map<String, List<DelegateMethod>> globalMethod = new HashMap<String, List<DelegateMethod>>() {
        @Override
        public List<DelegateMethod> get(Object key) {
            List<DelegateMethod> ret = super.get(key);
            if (ret == null) {
                ret = new ArrayList<>();
                super.put(key.toString(), ret);
            }
            return ret;
        }
    };
    final Map<String, Object> globalObject = new HashMap<>();


    @PostConstruct
    public void init() {
        Map<String, Object> mos = context.getBeansWithAnnotation(MoldObject.class);
        for (Map.Entry<String, Object> item : mos.entrySet()) {
            MoldObject moldObject = item.getValue().getClass().getAnnotation(MoldObject.class);

            for (MoldObject.Scope scope : moldObject.scope()) {
                if (scope == MoldObject.Scope.OBJECT) {
                    String name = moldObject.value();
                    if (name.isEmpty()) {
                        name = item.getValue().getClass().getSimpleName();
                    }
                    globalObject.put(name, item.getValue());
                }

                if (scope == MoldObject.Scope.METHOD) {
                    Method[] methods = item.getValue().getClass().getMethods();
                    for (Method method : methods) {
                        MoldMethod moldMethod = method.getAnnotation(MoldMethod.class);
                        if (moldMethod == null || moldMethod.ignore()) {
                            continue;
                        }
                        globalMethod.get(method.getName()).add(new DelegateMethod(item.getValue(), method));
                    }
                }
            }
        }
    }

    /**
     * 准备可以用的变量
     * @param baseMold
     * @return
     */
    public GlobalVal readyVar(BaseMold baseMold) {
        GlobalVal params = new GlobalVal();
        Set<Map.Entry<String, AbsVar>> entries = baseMold.getModel().entrySet();
        Map<String, DataProvider> beansOfType = context.getBeansOfType(DataProvider.class);
        for (Map.Entry<String, AbsVar> entry : entries) {
            DataProvider provider = beansOfType.entrySet().stream()
                    .filter(o -> o.getValue().supports(entry.getValue()))
                    .limit(1)
                    .findFirst()
                    .orElseThrow(() -> new NoSuchMessageException("没有找到支持 " + entry.getValue().getClass().getName() + "的 DataProvider, 是否忘记声明为 spring component了"))
                    .getValue();
            params.put(entry.getKey(), provider.get(entry.getValue()));
            params.put("_" + entry.getKey(), entry.getValue());
        }

        params.putAll(moldConfig.getEnvVar());
        params.putAll(globalObject);

        return params;
    }

    static class GlobalVal extends HashMap<String, Object> {

    }

    @AllArgsConstructor
    static class DelegateMethod {
        public Object target;
        public Method originMethod;
    }
    static class DelegateExecutor extends ReflectiveMethodExecutor {
        private DelegateMethod dm;

        public DelegateExecutor(DelegateMethod dm) {
            super(dm.originMethod);
            this.dm = dm;
        }

        @Override
        public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
            return super.execute(context, this.dm.target, arguments);
        }
    }
}
